#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <climits>
#include <set>
#include "Assert.h"

using namespace std;

// This takes a string with format '7:22.5' and returns 22.5 (along with 7 as outputFeatureNum)
double GetValueAndFeatureNumber(char* inputString, int* outputFeatureNum, int lineNum, int featureNum); 

int GetNumFeaturesNClasses(const char* fileName, int* numClasses);

// Usage: GenerateArffFiles input-file output-file numClasses numFeatures
int main(int argc, char* argv[]) {
  string line;
  ifstream infile; ofstream outfile;
  
  // Read input arguments
  ASSERT(argc == 3 || argc == 5) << "Incorrect number of arguments" << DIE;
  int numClasses = 0;
  int numFeatures = 0;
  if(argc == 3) {
    numFeatures = GetNumFeaturesNClasses(argv[1], &numClasses);  
  }
  else {
    numClasses = atoi(argv[3]);
    numFeatures = atoi(argv[4]);
  }
  
	infile.open (argv[1]); outfile.open (argv[2]);
  ASSERT(numClasses > 1 && numFeatures > 0) << "Invalid arguments: {numClasses:" << numClasses << ", numFeatures:" << numFeatures << "}" << DIE;
  
  outfile << "@relation myTempRel\n";
  for (int i = 0; i < numFeatures; i++) {
    outfile << "@attribute att" << i << " NUMERIC\n";
  }
  if(numClasses == 2) {
    outfile << "@attribute class        {class1, class-1}\n";
  }
  else {
    outfile << "@attribute class        {class1";
    for(int i = 2; i <= numClasses; i++) {
      outfile << ", class" << i;
    }
    outfile << "}\n";
  }
  outfile << "@data\n";
  long lineNum = 1; char arr[500];
  
  while(getline(infile,line)) {
    int numCommas = 0;
    if(line.compare("") == 0) {
      break; // break since empty line
    }
    char* myLine = new char[line.length() + 1];
    line.copy(myLine, line.length());
    myLine[line.length()] = '\0';
    char* pch = strtok(myLine," ");
    int featureNum = 0; 
    long classValue = 0;
    // string zerothFeatureVal; 
    bool atLeast1Value = false;
    while(pch != NULL) {
      if(strcmp(pch, "") == 0) {
        pch = strtok (NULL, " ");
        continue;
      }
      atLeast1Value = true;
      if(featureNum == 0) {
        //int classValue = atoi(pch);
        classValue = strtol(pch, NULL, 10);
        ASSERT(classValue != LONG_MAX && classValue != LONG_MIN) << "Incorrect class label in file " << argv[1] << ":" << lineNum << DIE;
        if(numClasses == 2) {
          ASSERT(classValue == 1 || classValue == -1) << "For 2 classes, Only values -1 and 1 should be used as class label, not " << classValue;
        }
        else {
          ASSERT(0 < classValue && classValue <= numClasses) << "For " << numClasses << " classes, the class labels needs to be in range [1, " << numClasses << "], not " << classValue << DIE;
        }
      }
      else {
        ASSERT(featureNum <= numFeatures) << "The featureNum (" << featureNum << ") cannot be greater than " << numFeatures << "See line " << lineNum << " in file " << argv[1] << DIE;
        ASSERT(strstr(pch, ":") != NULL) << "Incorrect format at line " << lineNum << " in file " << argv[1] << "\'" << pch << "\'" << DIE; 
        
        int myCurrFeatureNum = 0;
        strcpy(arr, pch);
        double myValue = GetValueAndFeatureNumber(arr, &myCurrFeatureNum, lineNum, featureNum);
        for(int i = featureNum; i < myCurrFeatureNum; i++) {
          outfile << 0 << ",";
          numCommas++;
          featureNum++;
        }
        outfile << myValue << ",";
        numCommas++;
      }
      
      featureNum++;
      pch = strtok (NULL, " ");
    }
    if(atLeast1Value) {
      for(int i = featureNum; i <= numFeatures; i++) {
        outfile << 0 << ",";
        numCommas++;
      }
      ASSERT(numCommas == numFeatures) << "Something wrong with logic." << DIE;
      outfile << "class" << classValue << "\n";
    }
    lineNum++;
    delete [] myLine;
  }
	infile.close();
  outfile.close();
	// cout << "Done GenerateArff" << argv[1] << argv[2] << "\n";
  return 0;
}

// This takes a string with format '7:22.5' and returns 22.5 (along with 7 as outputFeatureNum)
double GetValueAndFeatureNumber(char* inputString, int* outputFeatureNum, int lineNum, int featureNum) {
  for(int i = 0; i < 500; i++) {
    if(inputString[i] == ':') {
      inputString[i] = '\0';
      *outputFeatureNum = atoi(inputString);
      return atof(&inputString[i+1]);
    }
  }
  ASSERT(false) << "Error while generating Arff file >>" << inputString << "<< at line:" << lineNum << " and featureNum:" << featureNum << DIE; 
}

int GetNumFeaturesNClasses(const char* fileName, int* numClasses) {
  ifstream infile;
  infile.open(fileName);
  char arr[500];
  int maxFeatureNum = 0;
  int lineNum = 1; string line;
  set<int> classes;
  
  while(getline(infile,line)) {
    if(line.compare("") == 0) {
      break; // break since empty line
    }
    char* myLine = new char[line.length() + 1];
    line.copy(myLine, line.length());
    myLine[line.length()] = '\0';
    char* pch = strtok(myLine," ");
    int featureNum = 0; 
    while(pch != NULL) {
      if(strcmp(pch, "") == 0) {
        pch = strtok (NULL, " ");
        continue;
      }
      if(featureNum == 0) {
        int classValue = strtol(pch, NULL, 10);
        classes.insert(classValue);
      }
      else {
        ASSERT(strstr(pch, ":") != NULL) << "Incorrect format at line " << lineNum << " in file " << fileName << "\'" << pch << "\'" << DIE; 
        
        int myCurrFeatureNum = 0;
        strcpy(arr, pch);
        double myValue = GetValueAndFeatureNumber(arr, &myCurrFeatureNum, lineNum, featureNum);
        maxFeatureNum = maxFeatureNum > myCurrFeatureNum ? maxFeatureNum : myCurrFeatureNum;
      }
      featureNum++;
      pch = strtok (NULL, " ");
    }
    lineNum++;
  }
  *numClasses = classes.size();
  if(classes.size() == 2) {
    for(set<int>::iterator it= classes.begin(); it != classes.end(); it++) {
      ASSERT(*it == 1 || *it == -1) << "Class values need to be either -1 or 1 for 2 class problem" << DIE; 
    }
  }
  else {
    for(set<int>::iterator it= classes.begin(); it != classes.end(); it++) {
      ASSERT(*it > 0 && *it <= *numClasses) << "Class values need to be between [1, " << (*numClasses) << "] for  " << (*numClasses) << "-class problem in file: " << fileName << DIE; 
    }
  }
  
  ASSERT(*numClasses > 1) << "Incorrect number of classes in file " << fileName << DIE;
  ASSERT(maxFeatureNum > 0) << "Number of features has to be greater than 0, but it is " << maxFeatureNum << " for file: " << fileName << DIE;
  return maxFeatureNum;
}
